import { onMount, onCleanup } from "solid-js";
import { createStore } from "solid-js/store";
import { useNavigate, useParams } from "@solidjs/router";
import {
  onMessage,
  subscribe,
  unsubscribe,
  sendObjectWithTimestamp,
} from "tauri-plugin-libp2p-signal-api";

export default function useChat() {
  const navigate = useNavigate();
  const params = useParams();

  const [store, setStore] = createStore({
    error: null,
    peer: null,
    currentStream: null,
    currentUser: "",

    remoteStream: null,
    remoteUser: "",
    remoteMuted: false,
    remoteWebCam: true,

    incommingCall: false,
    incommingPayload: null,

    muted: false,
    webCam: true,

    topic: "",
  });

  //== 1. open local stream when mounted
  onMount(async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: true,
      });
      setStore("currentStream", stream);

      let chatCode = params.chatCode.trim();
      if (chatCode.length > 1) {
        let topic = "manju-chat: " + chatCode.slice(1);
        subscribe(topic);
        setStore("topic", topic);

        // sender
        if (chatCode.slice(0, 1) === "T") {
          videoChat(true);
        }
      }
    } catch (error: { name: string; message: string }) {
      console.error("media access permission error:-", error);
      setStore("error", { name: error.name, message: error.message });
    }
  });

  onCleanup(async () => {
    let topic = store.topic;
    if (topic.length > 0) {
      unsubscribe(topic);
    }
  });

  //== 2. register handlers for messages
  onMessage((message: string) => {
    try {
      let msg = JSON.parse(message);
      switch (msg.type) {
        case "video-offer":
          handleVideoOfferMsg(msg);
          break;
        case "video-answer":
          handleVideoAnswerMsg(msg);
          break;
        case "new-ice-candidate":
          handleNewICECandidateMsg(msg);
          break;
        case "remote-mic-toggle":
          handleRemoteMicToggle(msg);
          break;
        case "remote-cam-toggle":
          handleRemoteCamToggle(msg);
          break;
        case "end-call":
          endCall();
          break;
        default:
          break;
      }
    } catch (err) {
      console.log(err);
    }
  });

  async function handleVideoOfferMsg(msg: any) {
    await videoChat(false);

    let desc = new RTCSessionDescription(JSON.parse(msg.msg));
    let peer = store.peer as unknown as RTCPeerConnection;
    if (peer != null) {
      await peer.setRemoteDescription(desc);
      await peer.setLocalDescription(await peer.createAnswer());
      await sendObjectWithTimestamp({
        type: "video-answer",
        msg: JSON.stringify(peer.localDescription),
      });
    }
  }

  async function handleVideoAnswerMsg(msg: any) {
    let desc = new RTCSessionDescription(JSON.parse(msg.msg));
    let peer = store.peer as unknown as RTCPeerConnection;
    if (peer != null) {
      await peer.setRemoteDescription(desc).catch(reportError);
    }
  }

  async function handleNewICECandidateMsg(msg: any) {
    let candidate = new RTCIceCandidate(JSON.parse(msg.msg));
    try {
      let peer = store.peer as unknown as RTCPeerConnection;
      if (peer != null) {
        await peer.addIceCandidate(candidate);
      }
    } catch (err) {
      reportError(err);
    }
  }

  function handleRemoteMicToggle(msg: any) {
    let muted = JSON.parse(msg.msg);
    setStore("remoteMuted", muted.muted);
  }

  function handleRemoteCamToggle(msg: any) {
    let webCam = JSON.parse(msg.msg);
    setStore("remoteWebCam", webCam.webCam);
  }

  //== 3. the function to launch a video chat or response to it
  async function videoChat(isSender: boolean) {
    // create an RTCPeerConnection which knows to use our chosen STUN server.
    const peer = new RTCPeerConnection({
      iceServers: [
        {
          urls: "stun:stun.qq.com",
        },
      ],
    });
    setStore("peer", peer);

    // set up event handlers for the ICE negotiation process.
    peer.onicecandidate = handleICECandidateEvent;
    peer.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
    peer.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
    peer.onsignalingstatechange = handleSignalingStateChangeEvent;
    // pc.onnegotiationneeded = handleNegotiationNeededEvent;
    peer.ontrack = handleTrackEvent;

    // add local tracks
    let stream = store.currentStream as unknown as MediaStream;
    if (stream != null) {
      stream.getTracks().forEach((track: any) => peer.addTrack(track, stream));
    }

    // send offer
    if (isSender) {
      // create an offer as the local peer's current description.
      const offer = await peer.createOffer();
      await peer.setLocalDescription(offer);

      // send the offer to the remote peer.
      await sendObjectWithTimestamp({
        type: "video-offer",
        msg: JSON.stringify(peer.localDescription),
      });
    }
  }

  function handleICEConnectionStateChangeEvent(event: any) {
    console.log("*** ICEConnectionStateChange ***");
  }

  function handleICEGatheringStateChangeEvent(event: any) {
    console.log("*** ICEGatheringStateChange ***");
  }

  function handleSignalingStateChangeEvent(event: any) {
    console.log("*** SignalingStateChange ***");
  }

  async function handleICECandidateEvent(event: any) {
    if (event.candidate) {
      await sendObjectWithTimestamp({
        type: "new-ice-candidate",
        msg: JSON.stringify(event.candidate),
      });
    }
  }

  function handleTrackEvent(e: any) {
    setStore("remoteStream", e.streams[0]);
  }

  async function toggleMic() {
    setStore("muted", !store.muted);
    let stream = store.currentStream as unknown as MediaStream;
    if (stream) {
      stream.getAudioTracks()[0].enabled = !stream.getAudioTracks()[0].enabled;
    }

    await sendObjectWithTimestamp({
      type: "remote-mic-toggle",
      msg: JSON.stringify({ muted: store.muted }),
    });
  }

  async function toggleWebCam() {
    setStore("webCam", !store.webCam);
    let stream = store.currentStream as unknown as MediaStream;
    if (stream) {
      stream.getVideoTracks()[0].enabled = !stream.getVideoTracks()[0].enabled;
    }

    await sendObjectWithTimestamp({
      type: "remote-cam-toggle",
      msg: JSON.stringify({ webCam: store.webCam }),
    });
  }

  async function endCall() {
    let stream = store.currentStream as unknown as MediaStream;
    if (stream) {
      stream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
    }

    await sendObjectWithTimestamp({
      type: "end-call",
      msg: "",
    });

    navigate("/", { replace: true });
  }

  return {
    store,
    toggleMic,
    toggleWebCam,
    endCall,
  };
}
